/**
 * Moondance UML modeling tool
 * 
 * This program is free software; you can redistribute it and
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package ro.pub.cs.se.moondance.editpart;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Iterator;
import java.util.List;

import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;

import ro.pub.cs.se.moondance.editor.figures.ClassFigure;
import ro.pub.cs.se.moondance.editor.figures.CompositeStateFigure;
import ro.pub.cs.se.moondance.editor.figures.EditableLabel;
import ro.pub.cs.se.moondance.editor.figures.EndStateFigure;
import ro.pub.cs.se.moondance.editor.figures.PackageFigure;
import ro.pub.cs.se.moondance.editor.figures.RegionFigure;
import ro.pub.cs.se.moondance.editor.figures.StartStateFigure;
import ro.pub.cs.se.moondance.editor.figures.StateFigure;
import ro.pub.cs.se.moondance.editor.figures.StateMachineFigure;
import ro.pub.cs.se.moondance.model.ClassFunction;
import ro.pub.cs.se.moondance.model.ClassVariable;
import ro.pub.cs.se.moondance.model.Connection;
import ro.pub.cs.se.moondance.model.ModelElement;
import ro.pub.cs.se.moondance.model.UMLBaseClass;
import ro.pub.cs.se.moondance.model.UMLCompositeState;
import ro.pub.cs.se.moondance.model.UMLEndState;
import ro.pub.cs.se.moondance.model.UMLEntity;
import ro.pub.cs.se.moondance.model.UMLInterface;
import ro.pub.cs.se.moondance.model.UMLPackage;
import ro.pub.cs.se.moondance.model.UMLRegion;
import ro.pub.cs.se.moondance.model.UMLStartState;
import ro.pub.cs.se.moondance.model.UMLState;
import ro.pub.cs.se.moondance.model.UMLStateMachine;

@SuppressWarnings("unused")
public class UMLEntityEditPart extends AbstractGraphicalEditPart implements
		PropertyChangeListener {

	/**
	 * Upon activation, attach to the model element as a property change
	 * listener.
	 */
	public void activate() {
		if (!isActive()) {
			super.activate();
			((ModelElement) getModel()).addPropertyChangeListener(this);
		}
	}

	/**
	 * Upon reactivation, detach from the model element as a property change
	 * listener.
	 */
	public void deactivate() {
		if (isActive()) {
			super.deactivate();
			((ModelElement) getModel()).removePropertyChangeListener(this);
		}
	}

	/**
	 * to be overriden by subclasses
	 */
	protected void createEditPolicies() {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#createFigure()
	 */
	protected IFigure createFigure() {
		IFigure f = createFigureForModel();
		f.setOpaque(false);
		return f;
	}

	/**
	 * Return a IFigure depending on the instance of the current model element.
	 * This allows this EditPart to be used for both subclasses of Shape.
	 */
	private IFigure createFigureForModel() {
		if (getCastedModel() instanceof UMLBaseClass) {
			Figure classFigure;
			EditableLabel label = new EditableLabel(
					((UMLBaseClass) getCastedModel()).getLabelText(),
					EditableLabel.CLASS_NAME);
			if (getCastedModel() instanceof UMLInterface)
				classFigure = new ClassFigure(label, true);
			else
				classFigure = new ClassFigure(label, false);
			return classFigure;
		} else if (getCastedModel() instanceof ClassVariable) {
			ClassVariable attribute = (ClassVariable) getModel();
			String label = attribute.getLabelText();
			EditableLabel attributeLabel = new EditableLabel(label,
					EditableLabel.ATTRIBUTE_NAME);
			return attributeLabel;
		} else if (getCastedModel() instanceof ClassFunction) {
			ClassFunction operation = (ClassFunction) getModel();
			String label = operation.getLabelText();
			EditableLabel operationLabel = new EditableLabel(label,
					EditableLabel.OPERATION_NAME);
			return operationLabel;
		} else if (getCastedModel() instanceof UMLPackage) {
			EditableLabel label = new EditableLabel(
					((UMLPackage) getCastedModel()).getLabelText(),
					EditableLabel.PACKAGE_NAME);
			Figure packageFigure = new PackageFigure(label);
			return packageFigure;
		} else if (getCastedModel() instanceof UMLStartState) {
			EditableLabel label = new EditableLabel(
					((UMLStartState) getCastedModel()).getLabelText(),
					EditableLabel.START_STATE_NAME);
			Figure startStateFigure = new StartStateFigure(label);
			return startStateFigure;
		} else if (getCastedModel() instanceof UMLEndState) {
			EditableLabel label = new EditableLabel(
					((UMLEndState) getCastedModel()).getLabelText(),
					EditableLabel.END_STATE_NAME);
			Figure endStateFigure = new EndStateFigure(label);
			return endStateFigure;
		} else if (getCastedModel() instanceof UMLState) {
			EditableLabel label = new EditableLabel(
					((UMLState) getCastedModel()).getLabelText(),
					EditableLabel.STATE_NAME);
			Figure stateFigure = new StateFigure(label);
			return stateFigure;
		} else if (getCastedModel() instanceof UMLRegion) {
			EditableLabel label = new EditableLabel(
					((UMLRegion) getCastedModel()).getLabelText(),
					EditableLabel.REGION_NAME);
			Figure regionFigure = new RegionFigure();
			return regionFigure;
		} else if (getCastedModel() instanceof UMLCompositeState) {
			EditableLabel label = new EditableLabel(
					((UMLCompositeState) getCastedModel()).getLabelText(),
					EditableLabel.COMPOSITE_STATE_NAME);
			Figure stateFigure = new CompositeStateFigure(label);
			return stateFigure;
		} else if (getCastedModel() instanceof UMLStateMachine) {
			EditableLabel label = new EditableLabel(
					((UMLStateMachine) getCastedModel()).getLabelText(),
					EditableLabel.STATE_MACHINE_NAME);
			Figure machineFigure = new StateMachineFigure(label);
			return machineFigure;
		} else
			throw new IllegalArgumentException();
	}

	protected UMLEntity getCastedModel() {
		return (UMLEntity) getModel();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.gef.editparts.AbstractGraphicalEditPart#getModelSourceConnections
	 * ()
	 */
	protected List<Connection> getModelSourceConnections() {
		return getCastedModel().getSourceConnections();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.gef.editparts.AbstractGraphicalEditPart#getModelTargetConnections
	 * ()
	 */
	protected List<Connection> getModelTargetConnections() {
		return getCastedModel().getTargetConnections();
	}

	/**
	 * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
	 */
	public void propertyChange(PropertyChangeEvent evt) {
		String prop = evt.getPropertyName();
		if (UMLEntity.SIZE_PROP.equals(prop)
				|| UMLEntity.LOCATION_PROP.equals(prop))
			refreshVisuals();
		else if (UMLEntity.NAME.equals(prop))
			commitNameChange(evt);
		else if (ModelElement.CHILD.equals(prop))
			handleChildChange(evt);
		else if (UMLEntity.SOURCE_CONNECTIONS_PROP.equals(prop))
			refreshSourceConnections();
		else if (UMLEntity.TARGET_CONNECTIONS_PROP.equals(prop))
			refreshTargetConnections();
	}

	protected void refreshVisuals() {
		Rectangle bounds = new Rectangle(getCastedModel().getLocation(),
				getCastedModel().getSize());
		((GraphicalEditPart) getParent()).setLayoutConstraint(this,
				getFigure(), bounds);
	}

	/**
	 * called when child added or removed
	 */
	@SuppressWarnings("unchecked")
	protected void handleChildChange(PropertyChangeEvent evt) {

		// we could do this but it is not very efficient
		// refreshChildren();

		Object newValue = evt.getNewValue();
		Object oldValue = evt.getOldValue();

		if (!((oldValue != null) ^ (newValue != null))) {
			throw new IllegalStateException(
					"Exactly one of old or new values must be non-null for CHILD event");
		}

		if (newValue != null) {
			// add new child
			EditPart editPart = createChild(newValue);
			// int modelIndex =
			// getModelChildren().indexOf((ClassVariable)newValue);
			// System.out.println("index "+modelIndex);
			addChild(editPart, -1 /* modelIndex */);
		} else {

			List<EditPart> children = getChildren();

			EditPart partToRemove = null;
			for (Iterator<EditPart> iter = children.iterator(); iter.hasNext();) {
				EditPart part = iter.next();
				if (part.getModel() == oldValue) {
					partToRemove = part;
					break;
				}
			}

			if (partToRemove != null)
				removeChild(partToRemove);
		}
		getContentPane().revalidate();
	}

	/**
	 * Handles change in name - to be overridden by subclasses
	 */
	protected void commitNameChange(PropertyChangeEvent evt) {
	}

	/**
	 * handles change in bounds, to be overridden by subclass
	 */
	protected void handleBoundsChange(PropertyChangeEvent evt) {
	}
}
